//Just a helper function. Calls a specialized function flyer returns rows of enemies.

function zero() {
    return function (flyer) {
        return 0;
    };
}
function constant(num) {
    return function (flyer) {
        return num;
    };
}

function random(min, max) {
    return function (flyer) {
        var q = Math.floor(min + ((max - min) + 1) * Math.random());
        return q;
    };
}

/**Curved is special: by the nature of the function, it needs to set both the dx and dy functions
simultaneously. As a result, it needs to return an object instead of a plain old function. It complicates the overall API design,
which is unfortunate. In a language with operator overloading, this could be made slightly more pretty. Lacking that, the wrapper functions and the placement functions need to 
be made aware that they can be returned either a triplet or a single function.

As of right now, curved only supports reverse as a wrapper function. This should be extended at least
slightly at some point in the future.**/
function curved(x0, y0, x1, y1, x2, y2) {
    return function (flyer) {
        var min_y = Math.min(y0, y1, y2);
        if (min_y < 0) {
            y0 = y0 + min_y;
            y1 = y1 + min_y;
            y2 = y2 + min_y;
        }
        var min_x = Math.min(x0, x1, x2);
        if (min_x < 0) {
            x0 = x0 + min_x;
            x1 = x1 + min_x;
            x2 = x2 + min_x;
        }
        var func = function(that, a, b, c) {return 2 * that.t * (a -2*b + c) + 2*(b - a);};
        var dx = function () {return func(flyer, x0, x1, x2);};
        var dy = function () {return func(flyer, y0, y1, y2);};
        var dt = function () {return enemy_speed / Math.sqrt((Math.pow(dx(), 2) + Math.pow(dy(), 2)));};
        return [dx, dy, dt];
    };
}

function sin() {
    return function (flyer) {
        return Math.cos(flyer.t);
    };
}

function reverse(wrapped) {
    return function(flyer) {
        var wrapped_val = wrapped(flyer);
        var out = [];
		var i = 0;
        if (typeof wrapped_val === "object") {
            //Just negate dx and dy
            for (i=0; i < 2; i = i + 1) {
                out[i] = function () {
                    return -wrapped_val[i]();
                };
            }
            out[2] = wrapped_val[2];
        }
        else {
            out = -wrapped(flyer);
        }
        return out;
    };
}

/**Calling add with a curved function call is unsupported as it doesn't
make much sense. If you want to combine curve with another function, 
you need to split the curve call into its respective calls.**/
function add(func1, func2) {
    return function (flyer) {
        return func1(flyer) + func2(flyer);
    };
}

function loop() {
    var index = 0;
    var args = arguments;
    var delay = args[1];
    var choices = args[0];
    var tick = 0;
    return function (flyer) {
        if (tick >= delay) {
            if (index < (choices.length  - 1)) {
                index = index + 1;
            }
            else {
                index = 0;
            }
            tick = 0;
        }
        else {
            tick = tick + 1;
        }
        return choices[index](flyer);
    };
}

function enemy_wave(Type, dx, dy, func, count, canvas) {
    var num_created = 0;
    return function(transformation) {
        var enemies = [];
        var i = 0;
        if (num_created < count) {
            var positions = func(num_created);
            if (!(transformation === undefined)) {
                positions = transformation(positions);
            }
            for (i = 0; i < positions.length; i = i + 1) {
                var pos = positions[i];
                var x = pos[0];
                var y = pos[1];
                num_created = num_created + 1;
                var enemy = new Type(x, y, canvas);
                if (typeof dx === "object") {
                    if (dy !== null) {
                        log("When creating an enemy wave and using the Curved function for either dx or dy, the other motion function must be null.");
                        var err = new Error("blah");
                        log(err);
                    }
                }
                enemy.dx = function () {return dx(enemy);};
                enemy.dy = function () {return dy(enemy);};
                enemies.push(enemy);
            }
        }
        else {
            return false;
        }
        return enemies;
    };
}

function enemy_none(count) {
    var num_created = 0;
    return function() {
        if (num_created < count) {
            num_created = num_created + 1;
        }
        else {
            return false;
        }
        return [];
    };
}

function enemy_sin(Type, dx, dy, left, right, period, count, canvas) {
    return enemy_wave(Type, dx, dy, function (num_created) {return [[(left + right)/2 + ((right -left)/2) * Math.sin((2 * Math.PI * num_created) / period), -num_created*Type.prototype.height]];}, count, canvas);
}

function enemy_random(Type, dx, dy, left, right, count, canvas) {
    return enemy_wave(Type, dx, dy, function (num_created) {return [[left + Math.random()*(right-left), -num_created*Type.prototype.height]];}, count, canvas);
}    

function enemy_rows(Type, dx, dy, left, right, count, canvas) {
    //The last  +1 is a little sketchy. I need to put actual thought into figuring it out.
    var col_count = Math.floor((right - left) / (Type.prototype.width * 2)  +1);
    var row_count = Math.floor(count / col_count);
    var c = 0;
    return enemy_wave(Type, dx, dy, function (num_created) {
                            var row = [];
                            for (c = 0; c < col_count; c = c + 1) {
                               row.push([left + (2 * c) * Type.prototype.width, canvas.top -Math.round(num_created/col_count)*Type.prototype.height]);
                            }
                            return row;
                    }, count, canvas);
}
function enemy_line(Type, dx, dy, x0, y0, x1, y1, count, canvas) {
    var x_dist = x1 - x0;
    var y_dist = y1 - y0;
    var place;
    if (count > 1) {
        var x_space = x_dist / (count - 1);
        var y_space = y_dist / (count - 1);
        place = function (num_created) {
            return [[x0 + x_space*num_created, y0 + y_space*num_created]];
        };
    }
    else if (count === 1) {
        place = function (num_created) {
            return [[x0 + x_dist/2*num_created, y0 + y_dist/2*num_created]];
        };
    }

    return enemy_wave(Type, dx, dy, place, count, canvas);
}
    
function enemy_checkerboard(Type, dx, dy, left, right, count, canvas) {
    //The last  +1 is a little sketchy. I need to put actual thought into figuring it out.
    var col_count = Math.floor((right - left) / (Type.prototype.width*2) +1);
    var row_count = Math.floor(count / col_count);
    var flag = 0;
    var c = 0;
    var offset = function () {
        if (flag === 0) {
            flag = 1;
        }
        else {
            flag = 0;
            return 1;
        }
        return 0;
    };
        
    return enemy_wave(Type, dx, dy, function (num_created) {
                            var row = [];
                            var off = offset();
                            for (c = 0; c < col_count; c = c + 1) {
                               row.push([left + ((2 * c) + off) * Type.prototype.width, canvas.top + -Math.round(num_created/col_count)*Type.prototype.height]);
                            }
                            return row;
                    }, count, canvas);
}

function combine(groups) {
    var index= 0;
    var i = 0;
    var args = arguments;
    return function () {
        var merged = [];
        for (i = 0; i < args.length; i = i + 1) {
            var res = args[i]();
            if (res.length > 0) {
                merged.push.apply(merged, res);
            }
        }
        index = index + 1;
        if (merged.length === 0) {
            return false;
        }
        return merged;
    };
}
function delayed(func, time) {
    var delay = enemy_none(time); 
    return function() {
        var delay_value = delay();
        var out;
        //So long as enemy_none doesn't return false,
        //return whatever it returns
        if (!(delay_value === false)) {
            out = delay_value;
        }
        else {
            //If it returns false (signifying it's exhausted
            //its output supply, return the original function's
            //output
            out = func();
        }
        return out;
    };
}

function seperate(func, distance) {
    var previous_wave;
    var i = 0;
    var dist;
    return function() {
        if (!(distance.call === undefined)) {
            dist = distance();
        }
        else {
            dist = distance;
        }
        var out = func();
        if (!(previous_wave === undefined) && previous_wave.length > 0) {
            for (i = 0; i < out.length; i = i + 1) {
      
                out[i].y = Math.min(previous_wave[0].y - dist, out[i].y);
            }
        }
        previous_wave = out;
        return out;
    };
}

function rotate(func, angle) {
    var rads = 2 * Math.PI * (angle / 360);
    var cos = Math.cos(rads);
    var sin = Math.sin(rads);
    var i = 0;
    var rot = function(old) {
        var trans = [];
        for (i = 0; i < old.length; i = i + 1) {
            var untrans = old[i];
            var x = untrans[0];
            var y = untrans[1];
            trans.push([cos * x + (-sin) * y, sin*x + cos*y]);
        }
        return trans;
    };
    return function() { 
        return func(rot);
    };
}

function translate(func, dx, dy) {
    var move = function(old) {
        var i = 0;
        var trans = [];
        for (i = 0; i < old.length; i = i + 1) {
            var untrans = old[i];
            var x = untrans[0];
            var y = untrans[1];
            trans.push([x + dx, y + dy]);
        }
        return trans;
    };
    return function() { 
        return func(move);
    };
}
